import java.io.*;
import java.math.BigInteger;
import java.util.Hashtable;


public final class PacketBuffer {

	private PipedInputStream pipeIn;
	private DataInputStream dataIn;
	private Logger log;

	public PacketBuffer(String logFile) {
		this.log = new Logger(logFile);
		this.pipeIn = new PipedInputStream(1000000);
	}
	
	public void close() throws IOException
	{
		pipeIn.close();
	}

	public byte readByte() throws ParsePacketException
	{
		try {
			return dataIn.readByte();
		} catch (IOException e) {
			throw new ParsePacketException("Read Byte Exception", e);
		}
	}
	
	public void readByte(byte[] array, int i, int length) throws ParsePacketException
	{
		try {
			dataIn.read(array, i, length);
		} catch (IOException e) {
			throw new ParsePacketException("Read Byte[] Exception", e);
		}
	}
	
	public short readShort() throws ParsePacketException
	{
		try {
			return dataIn.readShort();
		} catch (IOException e) {
			throw new ParsePacketException("Read Short Exception", e);
		}
	}
	
	public int readInt() throws ParsePacketException
	{
		try {
			return dataIn.readInt();
		} catch (IOException e) {
			throw new ParsePacketException("Read Int Exception", e);
		}
	}
	
	public double readDouble() throws ParsePacketException
	{
		try {
			return dataIn.readDouble();
		} catch (IOException e) {
			throw new ParsePacketException("Read Double Exception", e);
		}
	}
	
	public long readLong() throws ParsePacketException
	{
		try {
			return dataIn.readLong();
		} catch (IOException e) {
			throw new ParsePacketException("Read Long Exception", e);
		}
	}
	
	public boolean readBool() throws ParsePacketException
	{
		try {
			return dataIn.readBoolean();
		} catch (IOException e) {
			throw new ParsePacketException("Read Boolean Exception", e);
		}
	}
	
	public float readFloat() throws ParsePacketException
	{
		try {
			return dataIn.readFloat();
		} catch (IOException e) {
			throw new ParsePacketException("Read Float Exception", e);
		}
	}
	
	public String readString() throws ParsePacketException
	{
		StringBuffer sb = new StringBuffer();
		short strLength;
		try {
			strLength = dataIn.readShort();

			for (short i = 0; i < strLength; i++)
			{
				sb.append(dataIn.readChar());
			}

		} catch (IOException e) {
			throw new ParsePacketException("Read String Exception", e);
		}
		return sb.toString();
	}

	public String readSlot() throws ParsePacketException
	{
		short itemID = readShort();
		
		if (itemID == -1)
			return "Empty slot";
		byte itemCount = readByte();
		short metaData = readShort();
		
		if (canEnchant(itemID))
		{
			short length = readShort();
			byte[] array = new byte[length];
			readByte(array, 0, length);
			StringBuffer buffer = new StringBuffer();
			
			for(byte b : array) {
			      buffer.append(String.format("%x", b));
			      buffer.append(" ");
			    }
			
			return itemID+"|"+String.format("%x", itemCount)+"|"+buffer.toString().toUpperCase();
		}
		else
			return itemID+"|"+String.format("%x", itemCount);
	}
	
	public Object readEMeta() throws ParsePacketException
	{
		Metadata[] metadatas = new Metadata[127];
		byte b = readByte();
		Object val;
		while (b != 127)
		{
			int index = b & 0x1F;
			int ty    = b >> 5;
			switch (ty)
			{
			case 0:
				val = readByte();
				break;
			case 1:
				val = readShort();
				break;
			case 2:
				val = readInt();
				break;
			case 3:
				val = readFloat();
				break;
			case 4:
				val = readString();
				break;
			case 5:
				val = new Hashtable<String,Object>(3);
				((Hashtable<String,Object>)val).put("id", readShort());
				((Hashtable<String,Object>)val).put("count", readByte());
				((Hashtable<String,Object>)val).put("damage", readShort());
				break;
			case 6:
				val = new int[3];
				for (int i = 0;i<3;i++)
					((Integer[]) val)[i] = readInt();
				break;
			default:
				throw new ParsePacketException("Read EntityMetadata");
			}
			metadatas[index] = new Metadata(val, ty);
			b = readByte();
		}
		return metadatas;

	}
	
	private static boolean canEnchant(short value)
    {
        return  (256 <= value && value <= 259) ||
                (267 <= value && value <= 279) ||
                (283 <= value && value <= 286) ||
                (290 <= value && value <= 294) ||
                (298 <= value && value <= 317) ||
                value == 261 || value == 359 ||
                value == 346;
    }

	
	public void connectPipe(PipedOutputStream pipeOut) throws IOException
	{
		pipeIn.connect(pipeOut);
		this.dataIn = new DataInputStream(pipeIn);
	}

}
